#include "GLGUI.h"
#include "../gl/mygl_ext.h"
#include "GLClient.h"
#include "../core/common.h"

//
// static
//

GLGuiType GLGuiControl::parseGuiType(const char* name)
{
    static String s_types[10] = {
        "desktop",
        "frame",
        "button",
        "checkbox",
        "textbox",
        "label",
        "hslider",
        "vslider",
        "listbox",
        "menu"
    };
	for (int i = 0; i < 10; i++)
	{
		if (s_types[i] == name)
			return (GLGuiType)i;
	}
	return GUI_Unknown;
}

int GLGuiControl::parseGuiAlign(const char* s)
{
    int a = 0;
    if (strstr(s, "top"))       a |= GUI_ALIGN_TOP;
    if (strstr(s, "bottom"))    a |= GUI_ALIGN_BOTTOM;
    if (strstr(s, "left"))      a |= GUI_ALIGN_LEFT;
    if (strstr(s, "right"))     a |= GUI_ALIGN_RIGHT;
    return a;
}


//
// GUI class
//

GLGuiControl::GLGuiControl(GLGuiControl* p, GLGuiType t, int x, int y, int w, int h) :
    type(t),
    position(x, y),
    size(w, h),
    parent(p)
{
    visible = true;
    enabled = true;
    font = NULL;
    textAlign = GUI_ALIGN_LEFT;
    value = 0;
    maxValue = 100;
    index = -1;
    topItem = 0;
    hoverIndex = 0;
    root = (p) ? p->root : this;
    client = (p) ? p->client : NULL;
    showSliders = false;
    id = 0;
    front_ctl = NULL;
    //ctl spec
    if (type == GUI_Button)
    {
        textAlign = GUI_ALIGN_CENTER;
    }
    else if (type == GUI_Listbox)
    {
        front_ctl = createControl(GUI_VSlider, w-20, 0, 20, h);
    }
    image = NULL;
    imageSize.set(mMin(size.x,size.y), mMin(size.x,size.y));
    updateCtlPos();
}

GLGuiControl::~GLGuiControl()
{
    foreach (GLGuiControl*, ctl, controls)
        delete ctl;
}

GLGuiControl* GLGuiControl::createControl(GLGuiType t, int x, int y, int w, int h)
{
    GLGuiControl* ctl = new GLGuiControl(this, t, x, y, w, h);
    ctl->setFont(font);
    controls.push_back(ctl);
    return ctl;
}

bool GLGuiControl::hitTest(int x, int y)
{
    if (x >= bounds.AA.x && y >= bounds.AA.y && x < bounds.BB.x && y < bounds.BB.y)
        return true;
    return false;
}

GLGuiControl* GLGuiControl::pickTopControl(int x, int y)
{
    if (visible && enabled && hitTest(x, y))
    {
        if (!controls.empty())
        {
            rforeach (GLGuiControl*, ctl, controls)
            {
                GLGuiControl* hc = ctl->pickTopControl(x, y);
                if (hc)
                    return hc;
            }
        }
        return this;
    }
    return NULL;
}

void GLGuiControl::setPosition(int x, int y, int w, int h)
{
    position.x = x;
    position.y = y;
    if (w>0) size.x = w;
    if (h>0) size.y = h;
    updateCtlPos();
    updateTextPos();
    updateListPos();
}

GLGuiControl* GLGuiControl::getControlById(int i)
{
    if (this->id == i)
        return this;
    foreach (GLGuiControl*, ctl, controls)
    {
        GLGuiControl* c2 = ctl->getControlById(i);
        if (c2)
            return c2;
    }
    return NULL;
}

void GLGuiControl::swapControls(GLGuiControl* c1, GLGuiControl* c2)
{
    if (!c1 || !c2 || c1==c2)
        return;
    std::list<GLGuiControl*>::iterator i1, i2;
    bool b1=false, b2=false;
    foreach (GLGuiControl*, ctl, controls)
    {
        if (ctl == c1)
        {
            i1 = get_iterator(ctl);
            b1 = true;
        }
        else if (ctl == c2)
        {
            i2 = get_iterator(ctl);
            b2 = true;
        }
    }
    if (b1 && b2)
    {
        *i1 = c2;
        *i2 = c1;
    }
}

void GLGuiControl::setSize(int w, int h)
{
    if (w>0) size.x = w;
    if (h>0) size.y = h;
    updateCtlPos();
    updateTextPos();
    updateListPos();
}

void GLGuiControl::updateCtlPos()
{
    int xx = position.x;
    int yy = position.y;
    GLGuiControl* ctl = parent;
    while (ctl)
    {
        xx += ctl->position.x;
        yy += ctl->position.y;
        ctl = ctl->parent;
    }
    bounds.set(xx, yy, xx+size.x, yy+size.y);
    if (parent && root)
    {
        if (bounds.BB.x > root->bounds.BB.x)
        {
            bounds.BB.x = root->bounds.BB.x;
            size.x = bounds.getWidth();
        }
        if (bounds.BB.y > root->bounds.BB.y)
        {
            bounds.BB.y = root->bounds.BB.y;
            size.y = bounds.getHeight();
        }
    }

    //other updates
    updateTextPos();
    updateListPos();
    updateImagePos();
    //update children
    foreach (GLGuiControl*, ctl2, controls)
        ctl2->updateCtlPos();
}

void GLGuiControl::updateTextPos()
{
    if (font)
    {
        int w = font->getFontInfo()->getStringWidth(text);
        int h = font->getFontInfo()->common.lineHeight;
        if (type == GUI_Textbox)
        {
            if (w > size.x - 10)
                textPos.x = bounds.BB.x - w - 7;
            else
                textPos.x = bounds.AA.x + 3;
        }
        else
        {
            //horizontal align
            if (textAlign & GUI_ALIGN_LEFT)
                textPos.x = bounds.AA.x + 3;
            else if (textAlign & GUI_ALIGN_RIGHT)
                textPos.x = bounds.BB.x - w - 3;
            else
                textPos.x = bounds.AA.x + size.x/2 - w/2;
        }
        if (front_ctl && !items.empty())
        {
            front_ctl->position.set(size.x-20, 0);
            front_ctl->size.set(20, size.y);
        }
        //vertical align
        if (textAlign & GUI_ALIGN_TOP)
            textPos.y = bounds.AA.y + 3;
        else if (textAlign & GUI_ALIGN_BOTTOM)
            textPos.y = bounds.BB.y - h - 3;
        else
            textPos.y = bounds.AA.y + size.y/2 - h/2;
        //other child controls
        foreach (GLGuiControl*, ctl, controls)
            ctl->updateTextPos();
    }
}

void GLGuiControl::updateListPos()
{
    if (front_ctl && !items.empty())
    {
        int n = items.size() * GUI_LISTITEM_HEIGHT - size.y;
        if (n <= 0)
        {
            front_ctl->visible = false;
            showSliders = false;
        }
        else
        {
            front_ctl->setMaxValue(n);
            front_ctl->visible = true;
            showSliders = true;
        }
    }
}

void GLGuiControl::updateImagePos()
{
    imagePos = rectf::bySize(bounds.AA.x, bounds.AA.y, imageSize.x, imageSize.y);
}

void GLGuiControl::drawControl(int status, bool in_focus)
{
    //status: 0-normal, 1-hovered, 2-clicked
    //style
    Color bc[] = { 0x40FFFFFF, 0x60FFFFFF, 0x80FFFFFF };
    Color fc[] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00 };
    Color tc = enabled ? Color::White : Color::Gray;
    //type
    switch (type)
    {
    case GUI_Desktop:
    case GUI_Menu:
        break;
    case GUI_Frame:
        {
            glFillRect(bounds, Color(0x80000000));
        }
        break;
    case GUI_Button:
        {
            glFillRect(bounds, bc[status]);
            glDrawRect(bounds, fc[status]);
            glDrawImage(image, imagePos);
            glDrawText(font, textPos.x, textPos.y, text, tc);
        }
        break;
    case GUI_Checkbox:
        {
            int y = bounds.getCenter().y;
            rectf r(bounds.AA.x, y-10, bounds.AA.x+20, y+10);
            glFillRect(r, bc[status]);
            glDrawRect(r, fc[status]);
            if (value)
            {
                rectf vr = r;
                vr.AA.x += 3;
                vr.AA.y += 3;
                vr.BB.x -= 3;
                vr.BB.y -= 3;
                glFillRect(vr, Color(0xA0FFFF00));
            }
            glDrawText(font, textPos.x + 25, textPos.y, text, tc);
        }
        break;
    case GUI_Textbox:
        {
            String s = text;
            if (in_focus) s += '_';
            glFillRect(bounds, Color(0xFFFFFFFF));
            glDrawRect(bounds, in_focus ? Color::Yellow : Color::Black);
            root->beginClipping(bounds);
            glDrawText(font, textPos.x, textPos.y, s, Color::Black);
            root->endClipping();
        }
        break;
    case GUI_Label:
        {
            glDrawText(font, textPos.x, textPos.y, text);
        }
        break;
    case GUI_HSlider:
        {
            rectf r = bounds;
            r.AA.y = bounds.getCenter().y - 10;
            r.BB.y = bounds.getCenter().y + 10;
            Color cm[4] = { 0x20FFFFFF, 0xA0FFFFFF, 0xA0FFFFFF, 0x20FFFFFF };
            glFillRectCM(r, cm);
            int x = mClamp(bounds.AA.x + ((float)value / maxValue) * size.x,
                           bounds.AA.x + 5, bounds.BB.x - 5);
            r.set(x-5, bounds.AA.y, x+5, bounds.BB.y);
            glFillRect(r, bc[status]);
            glDrawRect(r, fc[status]);
        }
        break;
    case GUI_VSlider:
        {
            rectf r = bounds;
            r.AA.x = bounds.getCenter().x - 10;
            r.BB.x = bounds.getCenter().x + 10;
            Color cm[4] = { 0x20FFFFFF, 0x20FFFFFF, 0xA0FFFFFF, 0xA0FFFFFF };
            glFillRectCM(r, cm);
            int y = mClamp(bounds.AA.y + ((float)value / maxValue) * size.y,
                           bounds.AA.y + 5, bounds.BB.y - 5);
            r.set(bounds.AA.x, y-5, bounds.BB.x, y+5);
            glFillRect(r, bc[status]);
            glDrawRect(r, fc[status]);
        }
        break;
    case GUI_Listbox:
        {
            if (parent && !parent->parent) // fix for popup menu background !!!
                glFillRect(bounds, 0xB0000000);
            else
                glFillRect(bounds, 0x20FFFFFF);
            glDrawRect(bounds, fc[status]);
            root->beginClipping(bounds);
            int x = bounds.AA.x + 3;
            int y = bounds.AA.y + 3 - topItem;
            for (int i = 0; i < (int)items.size(); ++i)
            {
                rectf r(bounds.AA.x, y-3, bounds.BB.x-(showSliders?20:0), y-3+GUI_LISTITEM_HEIGHT);
                if (i == index)
                    glFillRect(r, Color(0xA0FFFF00));
                if (i == hoverIndex && status > 0)
                    glFillRect(r, Color(0x40FFFFFF));
                glDrawText(font, x, y, items[i]);
                y += GUI_LISTITEM_HEIGHT;
            }
            root->endClipping();
        }
        break;
    case GUI_Unknown:
        {
            glDrawRect(bounds, Color::Red);
        }
        break;
    }

    //draw callback
    client->onGuiDraw(this);
}

void GLGuiControl::beginClipping(const rectf& clip)
{
	float sw = (float)client->getEngine()->getWidth() / (float)size.x;
	float sh = (float)client->getEngine()->getHeight() / (float)size.y;
    glEnable(GL_SCISSOR_TEST);
	glScissor(
		(int)(clip.AA.x * sw),
		(int)((size.y - clip.getHeight() - clip.AA.y) * sh),
		(int)(clip.getWidth() * sw),
		(int)(clip.getHeight() * sh));
}

void GLGuiControl::endClipping()
{
    glDisable(GL_SCISSOR_TEST);
}

void GLGuiControl::drawControls(GLGuiControl* hov_ctl, GLGuiControl* clk_ctl, GLGuiControl* act_ctl)
{
    if (!visible)
        return;
    int status = 0;
    if (this == hov_ctl)
    {
        if (this == clk_ctl)
            status = 2;
        else
            status = 1;
    }
    if (parent && parent->type == GUI_Listbox && parent->value == index)
        status = 3;
    drawControl(status, (this == act_ctl));
    //draw its controls
    if (!controls.empty())
    {
        foreach (GLGuiControl*, ctl, controls)
        {
            ctl->drawControls(hov_ctl, clk_ctl, act_ctl);
        }
    }
}

void GLGuiControl::addItem(const String& s)
{
    items.push_back(s);
    updateListPos();
}

const String& GLGuiControl::getItem(int index) const
{
    return items[index];
}

void GLGuiControl::clearItems()
{
    items.clear();
    updateListPos();
    index = -1;
}

void GLGuiControl::setTopItem(int t, bool update_slider)
{
    topItem = mClamp(t, 0, items.size() * GUI_LISTITEM_HEIGHT - size.y);
    if (update_slider)
    {
        if (front_ctl)
            front_ctl->setValue(topItem);
    }
}

int GLGuiControl::hitItem(int x, int y)
{
    if (!hitTest(x, y))
        return -1;
    int i = (y - bounds.AA.y + topItem) / GUI_LISTITEM_HEIGHT;
    if (i >= 0 && i < (int)items.size())
        return i;
    return -1;
}

void GLGuiControl::removeItem(int ind)
{
    std::vector<String>::iterator itr;
    int i = 0;
    for (itr = items.begin(); itr != items.end(); itr++)
    {
        if (i == ind)
        {
            items.erase(itr);
            if (index == i)
                index = -1;
            break;
        }
        i++;
    }
    updateListPos();
}

void GLGuiControl::fillItems(const char* itm_s)
{
    clearItems();
    foreach_word (s, itm_s, '|')
    {
        String ss(s);
        ss.trim();
        addItem(ss);
    }
}




